package core;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.Socket;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.UUID;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

public class Node {
	public String name;
	public String ip;
	public String DBUrl;
	public int load;
	public float rating;
	public float rec; // Relative effective capacity
	
	public HashMap<Node,Double> dist = new HashMap<Node,Double>();
	
	static public ArrayList<Node> NodeList = new ArrayList<Node>();
	
	public Node(String name, String ip, String url, int l, float r){
		this.name = name;
		this.ip = ip;
		DBUrl = url;
		load = l;
		rating = r;
	}
	
	public static void printNodeList(){
		System.out.println("Host List "+new SimpleDateFormat("[HH:mm:ss]").format(new Date())+":");
		for(Node node:NodeList){
			System.out.println(node.name+" ["+node.ip+"] ["+node.DBUrl+"] <"+node.load+"> ("+node.rating+")");
		}
		System.out.println();
	}
	
	public static void printNodeDist(){
		
		System.out.println("Node Distance List:");
		String out = "{";
		for(Node node : NodeList){
			out += "{";
			for(Node node2 : NodeList){
				out += node.dist.get(node2)+",";
			}
			out=out.substring(0,out.length()-1);out += "},\r\n";
		}
		out=out.substring(0,out.length()-3);
		out += "}";
		System.out.println(out);
		System.out.println();
	}
	
	/**
	 * For Graph initial assignment use
	 * @return
	 */
	public static Node getNode(){
		Node thisNode = null;
		float r = 0;
		for(Node node: Node.NodeList){
			if(node.rec > r){
				thisNode = node;
				r = node.rec;
			}
		}
		return thisNode;
	}
	
	/**
	 * For Balancer Use
	 * @return
	 */
	public static Node getFreeNode(){
		Node thisNode = null;
		float c = 0;
		for(Node node: Node.NodeList){
			if((5-node.load)*node.rating > c){
				thisNode = node;
				c = (5-node.load)*node.rating;
			}	
		}
		return thisNode;
	}
	
	public Node getNodeBi(){
		
		Node thisNode = null;
		float r = 0;
		//System.out.println("getNodeBi()");
		for(Node node: NodeList){
			//System.out.println(dist.get(node)+","+node.rec);
			if(dist.get(node) < 999 && node.dist.get(this)< 999){// 双通
				if(node.rec > r){
					thisNode = node;
					r = node.rec;
				}
			}
		}
		return thisNode;
	}
	
	public Node getNodeIn(){
		
		Node thisNode = null;
		float r = 0;
		//System.out.println("getNodeIn()");
		for(Node node: NodeList){
			//System.out.println(dist.get(node)+","+node.rec);
			if(node.dist.get(this) < 999){// 单进
				if(node.rec > r){
					thisNode = node;
					r = node.rec;
				}
			}
		}
		return thisNode;
	}
	
	public Node getNodeOut(){
		
		Node thisNode = null;
		float r = 0;
		//System.out.println("getNodeOut()");
		for(Node node: NodeList){
			//System.out.println(node.rec+"||"+dist.get(node));
			if(dist.get(node) < 999){// 单出
				if(node.rec > r){
					thisNode = node;
					r = node.rec;
				}
			}
		}
		return thisNode;
	}
	
	public static int getIndex(String ip){
		for(int i=0; i<NodeList.size(); i++){
			if(NodeList.get(i).ip.equals(ip)) return i;
		}
		return -1;
	}
	
	public static Node getNodeByIp(String ip){
		for(Node node: NodeList){
			if(node.ip.equals(ip)) return node;
		}
		return null;
	}
	
	public float rating(){
		return (float) (this.ratingTest(2000, 1)+
				this.ratingTest(2000, 2)+
				this.ratingTest(2000, 4)+
				this.ratingTest(2000, 8))/200;
	}
	
	public void updateDist(Node node, double dist2){
		if(this.dist.keySet().contains(node)){
			this.dist.remove(node);
			this.dist.put(node, dist2);
		} else this.dist.put(node, dist2);
	}
	
	public boolean loadEntity(JSONObject entity){
		
		Task task = new Task("LoadEntity");
		try {
			task.put("Entity", entity);
		} catch (JSONException e) {
			e.printStackTrace();
		}
		
		if(task.exec(this.ip)){
			return true;
		} else {
			System.out.println("\t"+task.toString());
			return false;
		}
	}
	
	public boolean loadProc(JSONObject proc){
		Task task = new Task("LoadProc");
		try {
			task.put("Process", proc);
		} catch (JSONException e) {
			e.printStackTrace();
		}
		
		this.load++;
		
		if(task.exec(this.ip)){
			return true;
		} else return false;
		
	}
	
	public boolean putTokens(String eid, JSONArray token){
		Task task = new Task("PutTokens");
		try {
			task.put("EntityId", eid);
			task.put("Tokens", token);
		} catch (JSONException e) {
			e.printStackTrace();
		}
		
		if(task.exec(this.ip)){
			return true;
		} else return false;
	}
	
	public boolean runProc(String pid){
		Task task = new Task("RunProc");
		try {
			task.put("ProcId", pid);
		} catch (JSONException e) {
			e.printStackTrace();
		}
		
		if(task.exec(this.ip)){
			return true;
		} else return false;
	}
	
	public boolean unloadProc(String pid){
		Task task = new Task("UnloadProc");
		try {
			task.put("ProcId", pid);
		} catch (JSONException e) {
			e.printStackTrace();
		}
		this.load--;
		
		if(task.exec(this.ip)){
			return true;
		} else return false;
	}
	
	public boolean setProcPriority(String pid, int priority){
		Task task = new Task("SetProcPriority");
		try {
			task.put("ProcId", pid);
			task.put("Priority", priority);
		} catch (JSONException e) {
			e.printStackTrace();
		}
		
		if(task.exec(this.ip)){
			return true;
		} else return false;
	}
	
	public boolean unloadEntity(String eid){
		Task task = new Task("UnloadEntity");
		try {
			task.put("EntityId", eid);
		} catch (JSONException e) {
			e.printStackTrace();
		}
		
		if(task.exec(this.ip)){
			return true;
		} else return false;
	}
	
	public JSONObject testServ(){
		Task task = new Task("TestServ");
		JSONObject receive = task.send(this.ip);
		receive.remove("id");
		return receive;
	}
	
	public boolean reset(){
		Task task = new Task("Reset");
		if(task.exec(this.ip)){
			return true;
		} else {
			System.out.println("Node: "+ip+"reset faild");
			return false;
		}
	}
	
	static public void resetAll(){
		for(Node node: Node.NodeList){
			node.reset();
		}
	}
	
	public JSONObject getInfo(){
		Task task = new Task("GetInfo");
		JSONObject receive = task.send(this.ip);
		receive.remove("id");
		return receive;
	}
	
	public JSONArray getAllTokens(String eid){
		Task task = new Task("GetAllTokens");
		try {
			task.put("EntityId", eid);
			return task.send(this.ip).getJSONArray("Tokens");
		} catch (JSONException e) {
			e.printStackTrace();
		}
		return null;
	}
	
	public int getTokenNum(String eid){
		Task task = new Task("GetTokenNum");
		try {
			task.put("EntityId", eid);
			return task.send(this.ip).getInt("TokenNum");
		} catch (JSONException e) {
			System.out.println(ip);
			e.printStackTrace();
		}
		return -1;
	}
	
	public static void main(String argv[]){
		Task task = new Task("Test");
		System.out.println(task.toString());
	}
	
	public int ratingTest(int time, int parallelFactor){
		
		Graph g = new Graph("Test Graph");
		
		//Set Entity 1
		Entity e1 = new Entity(g, "State1");
		e1.addVariable("$x","INT");
		e1.assign(this); // Assign Entity 1 to node
		
		//Set Entity 2
		Entity e2 = new Entity(g, "State2");
		e2.addVariable("$y","VARCHAR");
		e2.assign(this); // Assign Entity 2 to node
		
		//Set Process1
		Process p1 = new Process(g, "Process1", "TRUE");
		p1.addEntityIn(e1);
		p1.addEntityOut(e2);
		for(int i=0; i<1000; i++){
			p1.addEvalItem("$y=Sin[N[$x]]");
		}
		
		//Put tokens in Entity 1
		HashMap<String,String> token;
		ArrayList<HashMap<String,String>> tokens = new ArrayList<HashMap<String,String>>();
		for(int i=0;i<2000;i++){
			token = new HashMap<String,String>();
			token.put("$x", String.valueOf(i));
			tokens.add(token);
		}
		e1.putTokens(tokens);
		
		//Run Process 1
		for(int i=0;i<parallelFactor;i++){
			p1.implTo(this); // Remote load and run
		}

		// Sleep 10s and get result
		try {
			Thread.sleep(time);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		
		// Unload Process 1
		p1.unload();
		// Sleep 1, waiting to get the results.
		try {
			Thread.sleep(1000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		int num = e2.getResult().length();
		
		// Unload rest Entities
		e1.unload();
		e2.unload();
		
		return num;
	}

	public void clearDist() {
		dist = new HashMap<Node, Double>();
		dist.put(this, (double) 0);
	}
}

class Task extends JSONObject{
	Task(String type){
		try {
			this.put("id", UUID.randomUUID().toString());
			this.put("type", type);
		} catch (JSONException e) {
			e.printStackTrace();
		}
	}
	
	boolean exec(String ip){
		String buffer = "";
		try{
			Socket sock = new Socket(ip,12357);
			BufferedReader is = new BufferedReader(new InputStreamReader(sock.getInputStream(),"8859_1"));
			OutputStreamWriter osw = new OutputStreamWriter(sock.getOutputStream(),"8859_1");
			
			osw.write(this.toString() + "\r\n");
			osw.flush();
			buffer = is.readLine();
			String response = new JSONObject(buffer).getString("response");
			sock.close();
			
			//System.out.println(response);
			
			if(response.equals("Success")){
				return true;
			} else {
				System.out.println("\t"+buffer);
				return false;
			}
			
		} catch (IOException ex){
			ex.printStackTrace();
			return false;
		} catch (JSONException e) {
			e.printStackTrace();
			return false;
		}
	}
	
	JSONObject send(String ip){
		JSONObject receive = null;
		String response = null;
		try{
			Socket sock = new Socket(ip,12357);
			BufferedReader is = new BufferedReader(new InputStreamReader(sock.getInputStream(),"8859_1"));
			OutputStreamWriter osw = new OutputStreamWriter(sock.getOutputStream(),"8859_1");
			
			osw.write(this.toString() + "\r\n");
			osw.flush();
			response = is.readLine();
			receive = new JSONObject(response);
			sock.close();
			
		} catch (IOException e){
			e.printStackTrace();
		} catch (JSONException e) {
			System.out.println(ip+":"+response);
			e.printStackTrace();
		}
		return receive;
	}
}